using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

using Elephant.Framework;
using Elephant.Framework.Services;

using Elephant.Utility;

namespace Elephant.Utility.Components
{
    public class Mesh : DrawableContent
    {
        Matrix[] transforms;
        string assetName;
        bool isLoaded;

        Model model;

        Material material;

        // hack to force manually calling Draw()
        public override bool Visible
        {
            get
            {
                return false;
            }
            set
            {
                return;
            }
        }

        public override void LoadGraphicsContent(bool loadAllContent)
        {
            if (assetName != null && assetName.Length > 0) {
                try {
                    model = GameHelper.GetService<IContentService>().Load<Microsoft.Xna.Framework.Graphics.Model>(assetName);
                } catch (ContentLoadException) {
                    throw;
                } finally {
                    if (model != null) {
                        isLoaded = true;

                        transforms = new Matrix[model.Bones.Count];
                        model.CopyAbsoluteBoneTransformsTo(transforms);
                    }
                }
            }
        }

        public override void UnloadGraphicsContent(bool unloadAllContent)
        {
            transforms = null;
            model = null;

            isLoaded = false;
        }

        public override void Draw(GameTime gameTime)
        {
            // can't draw without a material
            if (material != null) {
                for (int i = 0; i < model.Meshes.Count; i++) {
                    model.Meshes[i].Draw();
                }
            }

            // TODO: since we're doing drawing in here, the renderer component won't be able to know what meshpart we're currently at,
            // and so it cant know which transform to multiply by, like in following example:
            /*
            // garbage in this loop somewhere
            foreach (ModelMesh mm in mesh.Model.Meshes) {
                foreach (BasicEffect effect in mm.Effects) {
                    effect.EnableDefaultLighting();

                    effect.View = camera.View;
                    effect.Projection = camera.Projection;

                    effect.World =
                        mesh.Transforms[mm.ParentBone.Index] *
                        transform.World;
                }

                mm.Draw();
            }*/
        }

        private void ApplyEffect(Effect effect)
        {
            for (int i = 0; i < model.Meshes.Count; i++) {
                for (int j = 0; j < model.Meshes[i].MeshParts.Count; j++) {
                    model.Meshes[i].MeshParts[j].Effect = material.Effect;
                }
            }
        }

        public string AssetName
        {
            get
            {
                return assetName;
            }
            set
            {
                if (assetName != value && value.Length > 0) {
                    assetName = value;

                    UnloadGraphicsContent(true);
                    LoadGraphicsContent(true);
                }
            }
        }

        public bool IsLoaded
        {
            get
            {
                return isLoaded;
            }
        }

        public Model Model
        {
            get
            {
                return model;
            }
        }

        public Matrix[] Transforms
        {
            get
            {
                return transforms;
            }
        }

        public Material Material
        {
            get
            {
                return material;
            }
            set
            {
                if (material != value) {
                    material = value;

                    ApplyEffect(material.Effect);
                }
            }
        }
    }
}
